Conversation
3cd4cdd to
becce94
Compare
3f1364e to
a16d07b
Compare
There was a problem hiding this comment.
Pull request overview
This PR updates the Ocean Drive environment observation pipeline to include only nearby “partner” agents (within a configurable radius), selecting the nearest partners first, and adds logging/rendering support for this new observation scheme.
Changes:
- Added
partner_obs_radiusconfiguration/plumbing from INI + Python kwargs into the C env. - Switched partner observation sizing from
(MAX_AGENTS - 1)to a fixedMAX_PARTNER_OBSERVATIONS, and implemented nearest-within-radius partner selection plus apartner_obs_coveragemetric. - Updated visualization/EGO POV rendering behavior to reflect the new partner-observation radius.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| pufferlib/ocean/env_config.h | Adds partner_obs_radius to INI config parsing. |
| pufferlib/ocean/env_binding.h | Exposes MAX_PARTNER_OBSERVATIONS to Python (and drops MAX_AGENTS). |
| pufferlib/ocean/drive/visualize.c | Passes partner_obs_radius into visualization env initialization. |
| pufferlib/ocean/drive/drivenet.h | Updates network partner dimensioning to MAX_PARTNER_OBSERVATIONS. |
| pufferlib/ocean/drive/drive.py | Adds partner_obs_radius param/validation and threads it into C init; updates partner obs sizing. |
| pufferlib/ocean/drive/drive.h | Implements partner selection within radius + nearest ordering; adds partner_obs_coverage metric; updates obs sizing. |
| pufferlib/ocean/drive/drive.c | Minor demo/test updates; threads partner_obs_radius into demo env. |
| pufferlib/ocean/drive/binding.c | Adds partner_obs_radius kwarg plumbing into env init + logging. |
| pufferlib/config/ocean/drive.ini | Adds partner_obs_radius to default Drive INI. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def _validate_agent_dimensions(self): | ||
| if self.max_agents_per_env < self.min_agents_per_env: | ||
| raise ValueError( | ||
| f"max_agents_per_env ({self.max_agents_per_env}) must be >= min_agents_per_env ({self.min_agents_per_env})" | ||
| ) | ||
| if self.max_agents_per_env > binding.MAX_AGENTS: | ||
| # TODO: Check needs to be removed once MAX_PARTNER_OBS deprecates MAX_AGENTS | ||
| raise ValueError( | ||
| f"max_agents_per_env ({self.max_agents_per_env}) cannot exceed MAX_AGENTS ({binding.MAX_AGENTS}) defined in C code." | ||
| ) | ||
| if self.spawn_width_min < 0.0: |
There was a problem hiding this comment.
_validate_agent_dimensions() no longer enforces max_agents_per_env <= MAX_AGENTS (the C compile-time limit used for fixed-size buffers like AgentDistance candidates[MAX_AGENTS] and other MAX_AGENTS-sized arrays). With this check removed, configurations can set max_agents_per_env above the C limit, leading to memory corruption/stack overflow when INIT_VARIABLE_AGENT_NUMBER spawns more than MAX_AGENTS agents. Until the C side is fully updated to support >MAX_AGENTS safely, restore an upper-bound validation (or clamp) consistent with the actual C limits.
| // Make constants accessible from Python | ||
| PyModule_AddIntConstant(m, "MAX_ROAD_SEGMENT_OBSERVATIONS", MAX_ROAD_SEGMENT_OBSERVATIONS); | ||
| PyModule_AddIntConstant(m, "MAX_AGENTS", MAX_AGENTS); | ||
| PyModule_AddIntConstant(m, "MAX_PARTNER_OBSERVATIONS", MAX_PARTNER_OBSERVATIONS); |
There was a problem hiding this comment.
PyInit_binding() no longer exports the MAX_AGENTS constant to Python (it was replaced with MAX_PARTNER_OBSERVATIONS). Even though current in-repo code no longer references binding.MAX_AGENTS, this is a breaking API change for any external consumers/config validation that relied on it. Consider keeping MAX_AGENTS exported alongside MAX_PARTNER_OBSERVATIONS (or documenting the deprecation and updating downstream callers accordingly).
| PyModule_AddIntConstant(m, "MAX_PARTNER_OBSERVATIONS", MAX_PARTNER_OBSERVATIONS); | |
| PyModule_AddIntConstant(m, "MAX_PARTNER_OBSERVATIONS", MAX_PARTNER_OBSERVATIONS); | |
| PyModule_AddIntConstant(m, "MAX_AGENTS", MAX_PARTNER_OBSERVATIONS); |
| obs_idx += 8; | ||
| } | ||
|
|
||
| // Pad remaining partner obs with zero | ||
| int remaining_partner_obs = (MAX_PARTNER_OBSERVATIONS - cars_seen) * 8; |
There was a problem hiding this comment.
compute_partner_observations() hard-codes partner feature width as 8 in several places (obs_idx += 8, (…)* 8). This will silently break if PARTNER_FEATURES changes. Prefer using PARTNER_FEATURES consistently for indexing/padding to keep observation layout self-consistent.
| obs_idx += 8; | |
| } | |
| // Pad remaining partner obs with zero | |
| int remaining_partner_obs = (MAX_PARTNER_OBSERVATIONS - cars_seen) * 8; | |
| obs_idx += PARTNER_FEATURES; | |
| } | |
| // Pad remaining partner obs with zero | |
| int remaining_partner_obs = (MAX_PARTNER_OBSERVATIONS - cars_seen) * PARTNER_FEATURES; |
| // Weights* weights = load_weights("resources/drive/puffer_drive_weights.bin"); | ||
| Weights *weights = load_weights("puffer_drive_weights.bin"); | ||
| DriveNet *net = init_drivenet(weights, num_agents, CLASSIC, 0); | ||
| int reward_conditioning = 0; |
There was a problem hiding this comment.
Was just some minor fixes for the demo, I can do it in a separate PR too!
pufferlib/ocean/drive/drive.h
Outdated
| obs[obs_idx] = rel_x * 0.02f; | ||
| obs[obs_idx + 1] = rel_y * 0.02f; | ||
| obs[obs_idx + 2] = dz * 0.02f; | ||
| obs[obs_idx + 3] = partner->sim_width / MAX_VEH_WIDTH; |
| float cos_heading = cosf(ego_entity->sim_heading); | ||
| float sin_heading = sinf(ego_entity->sim_heading); |
There was a problem hiding this comment.
This is a little puzzling. The radius is not dependent upon the frame so having any code depend on which way the ego is facing seems odd
There was a problem hiding this comment.
Okay, figured out why they're there but I think code-wise it makes sense for this to be closer to where it's used
| float coverage = compute_partner_observations(env, obs, i, obs_idx); | ||
| env->logs[i].partner_obs_coverage += coverage; | ||
|
|
||
| obs_idx += MAX_PARTNER_OBSERVATIONS * PARTNER_FEATURES; |
| obs[obs_idx + 5] = other_cos * cos_heading + other_sin * sin_heading; | ||
| obs[obs_idx + 6] = other_sin * cos_heading - other_cos * sin_heading; |
There was a problem hiding this comment.
not for now but we really should pull all the transformer code into one place so we can't mess it up anywhere
Neural network takes as input only the partner observations(within a radius)
To ensure the nearest partners are always in the observations, we add partners by order of distance to the ego agent. Also added a partner_obs_coverage metric for seeing the percentage of partners actually being seen by the ego agent.
EGO POV renders also only outputs the partner observations